/*
 * Copyright 2007 Matt Jensen
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.jtell.internal;

import org.jtell.CircularOrderException;
import org.jtell.EventSink;
import org.jtell.config.EventSinkMetadata;
import org.jtell.config.OrderMetadata;

import java.util.Comparator;
import java.util.Set;

/**
 * <p>
 * <code>EventSinkOrderComparator</code> is an implementation of the {@link Comparator} interface which orders
 * {@link EventSink} instances according to their embedded order token information. 
 * </p>
 * <p>
 * <strong>Thread Safety</strong><br/>
 * Instances of this class are safe for multithreaded access. 
 * </p>
 */
public class EventSinkOrderComparator implements Comparator<EventSink>
{
    /**
     * <p>
     * Construct a {@link EventSinkOrderComparator} instance.
     * </p>
     */
    private EventSinkOrderComparator()
    {
        super();
    }

    /* inherit javadoc */
    public int compare(final EventSink eventSink1, final EventSink eventSink2)
    {
        // We will need to examine the metadata for at least the first adapter.
        int result = 0;
        final EventSinkMetadata metadata1 = eventSink1.getEventSinkMetadata();
        if (!metadata1.isContributor() && !metadata1.isOrdered())
        {
            // If adapter 1 neither contributes tokens nor is ordered with respect to them, then it cannot possibly be
            // prioritized before adapter 2. Exit early in this case.
            result = 1;
        }
        else
        {
            // We will need to examine the metadata for both adapters.
            final EventSinkMetadata metadata2 = eventSink2.getEventSinkMetadata();
            if (!metadata2.isContributor() && !metadata2.isOrdered())
            {
                // If adapter 2 neither contributes tokens nor is ordered with respect to them, then it cannot possibly
                // be prioritized before adapter 1. Exit early in this case as well.
                result = -1;
            }
            else
            {
                // Both adapters are associated with contributions and/or orders. We will need to examine the metadata
                // fully.
                final Set<OrderMetadata> afterOrders1 = metadata1.getAfterOrders();
                final Set<OrderMetadata> beforeOrders1 = metadata1.getBeforeOrders();
                final Set<String> contributions1 = metadata1.getContributions();
                final Set<OrderMetadata> afterOrders2 = metadata2.getAfterOrders();
                final Set<OrderMetadata> beforeOrders2 = metadata2.getBeforeOrders();
                final Set<String> contributions2 = metadata2.getContributions();

                // Must adapter 1 come before adapter 2?
                final boolean firstEventSink1 = containsTokenInOrders(contributions1, afterOrders2) ||
                        containsTokenInOrders(contributions2, beforeOrders1);
                final boolean firstEventSink2 = containsTokenInOrders(contributions2, afterOrders1) ||
                        containsTokenInOrders(contributions1, beforeOrders2);
                if (firstEventSink1 && firstEventSink2)
                {
                    // Adapters depend upon each other!
                    throw new CircularOrderException(String.format(
                            "Circular order between event sink adapters [%s] and [%s].", eventSink1, eventSink2),
                            eventSink1, eventSink2);
                }
                else if (firstEventSink1)
                {
                    // Second depends on first.
                    result = -1;
                }
                else if (firstEventSink2)
                {
                    // First depends on second.
                    result = 1;
                }
            }
        }
        return result;
    }

    /**
     * <p>
     * Determine whether a contribution token set contains a token from a set of order metadata entries. Used to
     * determine whether one adapter contributes a token by which another is ordered.
     * </p>
     *
     * @param contributions the contribution tokens.
     * @param orders the order entries.
     * @return <code>boolean</code> <code>true</code> if the contribution token set contains at least one token which is
     * referenced in the order entries set.
     */
    private boolean containsTokenInOrders(final Set<String> contributions,
                                          final Set<OrderMetadata> orders)
    {
        boolean result = false;
        for (final OrderMetadata order : orders)
        {
            if (contributions.contains(order.getToken()))
            {
                result = true;
                break;
            }
        }
        return result;
    }

    /**
     * <p>
     * The singleton instance.
     * </p>
     */
    public static final Comparator<EventSink> INSTANCE = new EventSinkOrderComparator();
}
